menu
  Home  ==>  articles  ==>  web  ==>  services_web  ==>  delphi_web_services   

Services Web Delphi - John COLIBRI.


1 - Principe des Web Services

Le but fondamental d'un Service Web est de récupérer sur un poste le résultat d'un calcul effectué sur un autre poste.

Voici quelques exemples simples:

  • consultation de la météo
  • consultation et réservation de trains / avions / hôtels
  • récupération de cotations boursières et calculs sur ces valeurs
Pour des documents plus lourds:
  • traduction de textes (BabelFish)
  • téléchargement de carte en fournissant des coordonnées (longitude, lattitude) ou une adresse postale (Mappoint)
  • consultation de produits Amazon
Outre le dialogue en utilisant ou non un explorateur internet, les services web permettent aussi d'effectuer des traitement distants entre deux application ("Business to Business, ou encore B2B). Par exemple:
  • un libraire (un point de vente quelconque, une usine) vend (consomme) des produits. Lorsque le stock attend un certain seuil, il faut réapprovisionner. La façon traditionnelle est de téléphoner ou d'envoyer un fax. Un peu mieux, utiliser Internet. Et, naturellement, sans aucune intervention humaine, un Service Web. Par exemple le logiciel de déstockage contient les seuils de réapprovisionnement, et dès que celui ci est franchi, une commande est envoyée automatiquement
  • tout système de surveillance (un laminoir)


2 - Construction d'un Service Web

2.1 - Fonctionnement des Web Services

Nous allons illustrer ceci sur un exemple très simple de conversion monétaire: conversion de Dollars en Euros (et réciproquement éventuellement).

Pour effectuer ce calcul:

  • l'utilisateur lance une application de conversion qui contient l'adresse du Service Web de conversion, tape une valeur en Dollars et clique sur un bouton
  • le Service Web reçoit la demande, calcule le résultat, et renvoit la réponse
  • l'utilisateur voit le résulat de la conversion


Ainsi présenté, ce type de traitement est clairement du type Client / Serveur. Voici le déroulement du dialogue
   le Serveur est lancé. Il contient
  • un exécutable (.EXE ou .DLL) contenant les FUNCTIONs et des PROCEDUREs que des Clients peuvent invoquer. Dans notre cas une fonction qui convertit des Dollars en Euros:

    Function f_euro(p_dollarInteger): Integer

  • un Serveur TCP/IP capable de recevoir une demande Client, lancer l'exécutable, et renvoyer la réponse
Voici notre Serveur à l'écoute des Clients:

the_web_service_server_listens

   un client lance un .EXE qui contient un Service Web Client. Il demande la conversion d'un montant en Dollar:

web_service_client_request

   le Serveur TCP/IP reçoit la requête, et charge l'exécutable qui fait la conversion :

the_server_loads_the_web_service_executable

   the TCP/IP Server renvoie la réponse:

the_web_service_server_answers



Vous aurez remarqué que ce type d'échange est très similaire aux échanges CGI ou ISAPI, à la différence que le Client n'a pas besoin d'être un exlorateur Web, et la requête n'est pas provoquée par un clic sur un bouton <INPUT situé dans une balise <FORM d'une page .HMTL. Le Client est contenu dans une application Windows normale (un .EXE).



2.2 - Les Composants Nécesaires

Un service Web a besoin pour fonctionner
  • d'un réseau TCP/IP
  • d'un serveur HTTP capable de comprendre le protocole des Services Web
  • d'une extension du serveur, CGI ou ISAPI, qui contient le traitement effectué par le service
  • de clients qui vont interroger le Service pour obtenir des résultats


2.3 - Le Réseau TCP/IP

Celui-ci est disponible par défaut sur la plupart des OS, dont Windows XP et similaires



2.4 - Le protocole SOAP

2.4.1 - SOAP= HTTP + XML

L'exécution depuis un poste d'un traitement sur un autre fait partie des techniques RPC (Remote Procedure Call). Parmi les moyens pour mettre en oeuvre des RPC, citons
  • CORBA (Common Object Request Broker Architecture, qui est très complet, mais très vaste et très complexe
  • DCOM (Distributed Component Object Model) qui est l'extension "distribuée" de COM. Solution purement Microsoft, et peu à peu tombé en désuétude à cause de sa difficulté de mise en oeuvre
  • et finalement les Services Web utilisant le protocole SOAP (Simple Object Access Protocol
SOAP spécifie simplement comment se déroulera le dialogue entre le Serveur et les Clients. Devant le refus de DCOM, Microsoft a sous-traité à Don BOX la création d'un nouveau protocole, et le résultat a été SOAP.

SOAP se veut une simplification par rapport à DCOM. Aussi, Don BOX a choisi:

  • d'utiliser le réseau TCP/IP
  • le protocole HTTP
  • un contenu au format .XML
Donc:
  • TCP/IP pour son ubiquité
  • HTTP car c'est le protocole utilisé par le Web (serveurs et explorateurs Web). Donc universellement accepté, et utilisant sur les PC le port 80, qui est pratiquement toujours laissé libre par les pare feu (sinon vous n'avez pas accès au Web, ce qui de nos jours est terrible)
  • pour poser la question, une simple requête au format CGI aurait en général suffi. Mais la réponse peut être complex (une Table complète, par exemple), il fallait trouver un format adéquat. XML (eXtended Markup Language) a été retenu.


2.4.2 - Le Format SOAP

Rassurez-vous, vous n'aurez ni à construire les requêtes au format HTTP, ni a gérer ce protocole, ni à écrire ou lire du .XML. Les couches Delphi se chargeront de tout.

Néanmoins présentons rapidement le contenu des données échangées:
   lorsque le Client envoie sa requête, le Client envois au Serveur un texte composé d'une en-tête HTTP et d'un contenu avec la fonction appelée et ses paramètres au format .XML:

 
POST /p_server_euro.co_euro/soap/i_server_euro HTTP/1.1
SOAPAction: "urn:u_i_server_euro-i_server_euro#f_euro"
Content-Type: text/xml
User-Agent: Borland SOAP 1.2
Host: localhost
Content-Length: 525
Connection: Keep-Alive
Cache-Control: no-cache
 
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    // -- ...ooo... Ed: truncated >
  <SOAP-ENV:Body SOAP-ENV:encodingStyle=
      "http://schemas.xmlsoap.org/soap/encoding/">
    <NS1:compute_euro xmlns:NS1="urn:u_i_server_euro-i_server_euro">
      <p_euro xsi:type="xsd:int">30</p_euro>
    </NS1:compute_euro>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

où:

  • p_server_euro est le nom de notre exécutable (p_server_euro.exe)
  • i_server_euro est le nom de l'INTERFACE Delphi qui définit les méthodes disponibles
  • f_euro est la fonction appelée
  • 30 est le montant en Dollars
   le Serveur retourne la réponse avec le message suivant:

 
HTTP/1.1 200 OK
Content-Type: text/xml
Content-Length: 465
Content:
 
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    // -- ...ooo... Ed: truncated >
  <SOAP-ENV:Body SOAP-ENV:encodingStyle=
      "http://schemas.xmlsoap.org/soap/encoding/">
    <NS1:f_euroResponse xmlns:NS1="urn:u_i_server_euro-i_server_euro">
      <return xsi:type="xsd:int">15</return>
    </NS1:f_euroResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

15 est le résultat en Euros



Les messages comportent donc

  • l'en-tête HTTP classique
  • une "enveloppe" au format .XML qui contient soit la requête soit la réponse


2.5 - WSDL

Nous allons écrire un Serveur de Services Web et un Client de Services Web.

Si nous souhaitions consulter les livres Amazon ou réserver un train, le Serveur de Service Web est déjà écrit (par la SNCF). Il nous suffit de créer le Client Web Service.

Il faut toutefois connaître :

  • le nom des méthodes offertes par le service SNCF
  • le nombre et le type des paramètres de chaque méthode
Ces informations sont disponibles dans un document .WSDL (Web Service Definition Language, prononcé "ouicedelle"). Voici le .WSDL de notre requête

 
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
    // -- ...ooo... Ed: truncated
    name="i_server_euroservice"
    // -- ...ooo... Ed: truncated >
  <message name="f_euro0Request">
    <part name="p_euro" type="xs:int"/>
  </message>
  <message name="f_euro0Response">
    <part name="return" type="xs:int"/>
  </message>
  <message name="compute_euro1Request">
    <part name="p_euro" type="xs:int"/>
  </message>
  <message name="compute_euro1Response"/>
 
  // -- ...ooo... Ed: truncated



Vous distinguerez dans ce document (tronqué: il fait environ 50 lignes) les noms du service, de notre fonction f_euro et le paramètre p_euro et son type Int. Comme nous n'aurons pas à écrire nous-même ce type de données, nous ne nous attarderons pas sur son contenu.

Le point important est que, pour écrire un Client Web Service, nous pouvons interroger le Serveur Web Service pour lui demander quels service il offre.

Une fois ce document récupéré, nous pouvons créer le Client.

Delphi va même plus loin en offrant un Wizard qui à partir du document .WSDL va générer un UNITé qui importer l'INTERFACE permettant de dialoguer avec le Serveur. Nous utiliserons ce Wizard d'importation .WSDL ci-dessous.



2.6 - Le Serveur HTTP

Il suffit que le poste serveur dispose d'un Serveur HTTP. Parmi les possibilités, citons
  • sur Windows 95 et Windows 98, PWS (Personal Web Server)
  • sur XP Home, aucun serveur n'est fourni par Microsoft. Nous pouvons toujours acheter er installer IIS (Internet Information Server) ou Appache. Nous pouvons utiliser des solutions purement Delphi:
    • depuis Delphi 6, le WAD (Web Application Debugger)
    • divers serveurs construits à partir des composants TCP/IP Indy
    • nous avons nous-mêmes utilisé un serveur HTTP CGI construit à partir de nos sockets, et adapté pour les Services Web
  • pour XP Pro, nous disposons d'une version IIS light


Dans cet article, nous utiliserons les Serveur HTTP de débugging intégré à Delphi, WAD.



2.7 - Type d'exécutable Service Web

L'exécutable qui contiendra nos méthodes et qui sera lancé par le Serveur TCP/IP peut avoir trois formats:
  • une .DLL ISAPI (Internet Server API) ou NSAPI (NetScape API
  • une extension serveur CGI (Common Gateway Interface)
  • une application incluant le Serveur HTTP et une extension CGI
Les deux premières solutions nécessitent l'existence d'un Serveur HTTP (IIS, Appache ou autre), alors que la troisième emploie le Serveur Indy de mise au point intégré à Delphi, WAD.

Nous utiliserons WAD, qui est fourni avec Delphi




3 - Le Serveur Web Service

3.1 - Construction de l'extension Serveur

3.1.1 - Mise en place en utilisant le Wizard

Voici les étapes pour créer notre service de conversion:
   démarrez Delphi 2006 Windows
   sélectionnez le Wizzard qui crée une application de Service Web en sélectionnant "File | New | Other | Delphi Project | Web Services | Soap Server"

soap_server_application

   le Wizard nous propose 3 solutions:

3_web_service_servers

Ces possibilités correspondent:

  • une .DLL ISAPI (Internet Server API) ou NSAPI (NetScape API
  • une extension serveur CGI (Common Gateway Interface)
  • une application incluant le Serveur HTTP et une extension CGI
Comme indiqué ci-dessus, nous utiliserons la troisième solution qui fonctionne avec le Serveur HTTP Indy fourni avec Delphi et qui en plus permet de débugger notre Serveur

   sélectionnez "Web App Debugger" (alias WAD)
   la boite d'édition devient accessible
   tapez le nom d'une co-classe. Nous suggérons co_euro_dollar, puis tapez "ok"

   Delphi nous demande si nous souhaitons créer automatiquement l'INTERFACE et l'IMPLEMENTATION de noter Service:

create_web_service_interface

   tapez "Yes"

   Delphi nous demande le nom de notre Service
   nous suggérons d'appeler l'INTERFACE, et par conséquent vous pouvez taper
  • dans "Service Name, "_server_euro_dollar" (le I sera ajouté automatiquement)
  • dans "Unit Identifier" "u_i_server_euro_dollar"
Supprimez aussi "Generate Comments" pour alléger le texte

add_new_webservice

Puis cliquez "Ok"

   Delphi génère les fichiers nécessaires au Serveur de Services Web


Ces fichiers comprennent:
  • le projet (.DPR)
  • l'UNITé qui contient l'INTERFACE, appelée actuellement U_I_SERVER_EURO_DOLLARINTF
  • l'UNITé qui contient la CLASSe qui implémentera not INTERFACE, appelée U_I_SERVER_EURO_DOLLARIMPL
  • un tWebModule permettant de réceptionner les requêtes du Client Web Service, appelé actuellement UNIT2
  • une tForm, qui actuellement ne contient aucun composant, mais qui sera affichée chaque fois que le Serveur est activé


Nous suggérons de renommer les différentes parties de la façon suivante:
   sélectionnez "Project Manager | PROJECT1.EXE | click droit | Rename" et tapez P_SERVER_EURO_DOLLAR_WAD
   renommez U_I_SERVER_EURO_DOLLARINTF U_I_SERVER_EURO_DOLLAR
   renommez U_I_SERVER_EURO_DOLLARIMPL U_C_SERVER_EURO_DOLLAR
   renommez UNIT2 U_WM_SERVER_EURO_DOLLAR
   renommez UNIT1 U_F_SERVER_EURO_DOLLAR
Crée un répertoire SERVER_WAD, et sélectionnez "File | Save All"



3.1.2 - Définition des méthodes du Service Web

Il faut à présent placer dans l'INTERFACE les méthodes que nous souhaitons utiliser. Nous allons utiliser une FUNCTION pour passer du dollar à l'euro, et une PROCEDURE pour la conversion inverse.

Par conséquent:
   sélectionnez "Project Manager | u_i_server_euro_dollar".
   le code généré par le wizzard contient les importations, une INTERFACE actuellement vide, et une initialisation que enregistre notre INTERFACE
   dans le code, ajoutez la définition de nos deux méthodes:

unit u_i_server_euro_dollar;
  interface
    uses InvokeRegistryTypesXSBuiltIns;

    type i_server_euro_dollar=
           interface(IInvokable)
             ['{54C806EE-35DD-4F1C-8530-4B31F1DBFE3A}']
             function f_dollar_to_euro(p_dollarDouble): DoubleStdCall;
             procedure euro_to_dollar(p_euroDouble;
                 var pv_dollarDouble); StdCall;
           end// i_server_euro_dollar

  implementation

  initialization
    InvRegistry.RegisterInterface(TypeInfo(i_server_euro_dollar));
  end.




Notez que

  • notre INTERFACE i_server_euro_dollar descend de iInvokable. C'est ce qui permettra de provoquer un appel depuis le Client Web Service
  • les méthodes DOIVENT avoir l'attribut STDCALL pour pouvoir être invoquées
  • pour pouvoir retrouver cette INTERFACE parmi d'autres qui seraient éventuellement disponibles sur le même Serveur, Delphi a placé automatiquement un GUID (Globally Unique Identifier). Nous pouvons changer ce numéro tant que nous le souhaitons en tapant Shift-Ctrl-G. La valeur de cet identifiant unique a pour seul but de pouvoir identifier notre INTERFACE. Ce GUID ne sera pas directement utilisé par notre code dans la suite (il ne sera pas invoqué par notre code, ou par le Client). De plus ce GUID ne correspond pas à un objet COM. C'est uniquement un moyen d'identification unique.
  • InvRegistry.RegisterInterface permet de stocker les informations qui permettront d'appeler la CLASSe qui implémente notre INTERFACE. Notez que cet enregistrement est purement temporaire. Rien ne sera stocké à ce niveau dans la base de registres (Registry). La table de correspondance entre le nom "i_server_euro_dollar" et l'INTERFACE i_server_euro_dollar est créée lorsque notre CGI Serveur est chargé


3.1.3 - L'implémentation de notre INTERFACE

Il nous faut à présent implémenter les méthodes que nous avons proposé dans notre INTERFACE.

Par conséquent:
   copiez les deux méthodes que nous avons placées dans l'INTERFACE dans le presse-papier
   sélectionnez "Project Manager | u_c_server_euro_dollar".
   le code généré par le wizzard contient les importations, une CLASS actuellement vide et une intialization que enregistre notre CLASSe
   modifiez le nom de l'UNITé qui contient notre INTERFACE en U_I_SERVER_EURO_DOLLAR
   collez les deux définitions de méthode dans la CLASSe
   tapez Shift-Ctrl-C pour générer les implémentations de nos deux méthodes
   dans ces méthodes, tapez les calculs de conversion

Le résultat est:

unit u_c_server_euro_dollar;
  interface
    uses InvokeRegistryTypesXSBuiltIns
        , u_i_server_euro_dollar;

    type c_server_euro_dollar=
           class(TInvokableClassi_server_euro_dollar)
             public
               function f_dollar_to_euro(p_dollarDouble): DoubleStdCall;
               procedure euro_to_dollar(p_euroDouble;
                   var pv_dollarDouble); StdCall;
           end// c_server_euro_dollar

  implementation

    // -- c_server_euro_dollar

    function c_server_euro_dollar.f_dollar_to_euro(p_dollarDouble): Double;
      begin
        Result:= p_dollar / 1.51;
      end// f_dollar_to_euro

    procedure c_server_euro_dollar.euro_to_dollar(p_euroDouble;
        var pv_dollarDouble);
      begin
        pv_dollar:= p_euro* 1.51;
      end// euro_to_dollar

      initialization
        InvRegistry.RegisterInvokableClass(c_server_euro_dollar);
      end.




Notez que:

  • notre CLASSe doit descendre de tInvokableClass, et de notre INTERFACE i_server_euro_dollar
  • l'initialisation de notre UNITé appelle InvRegistry.RegisterInvokableClass qui stocke dans notre base de correspondance notre CLASSe


3.1.4 - Le WebModule

Nous n'avons pas besoin de modifier ce module, mais nous allons le présenter rapidement.

Le code est le suivant

unit u_wm_server_euro_dollar;
  interface
    uses SysUtilsClassesHTTPAppInvokeRegistryWSDLIntfTypInfo,
        WebServExpWSDLBindXMLSchemaWSDLPubSOAPPasInvSOAPHTTPPasInv,
        SOAPHTTPDispWebBrokerSOAP;

    type TWebModule1=
           class(TWebModule)
               HTTPSoapDispatcher1THTTPSoapDispatcher;
               HTTPSoapPascalInvoker1THTTPSoapPascalInvoker;
               WSDLHTMLPublish1TWSDLHTMLPublish;
               procedure WebModule1DefaultHandlerAction(SenderTObject;
                 RequestTWebRequestResponseTWebResponsevar HandledBoolean);
             private
             public
           end// TWebModule2

    var WebModule1TWebModule1;

  implementation
    uses WebReq;

    {$R *.dfm}

    procedure TWebModule1.WebModule1DefaultHandlerAction(SenderTObject;
        RequestTWebRequestResponseTWebResponsevar HandledBoolean);
      begin
        WSDLHTMLPublish1.ServiceInfo(SenderRequestResponseHandled);
      end// WebModule1DefaultHandlerAction

    initialization
      WebRequestHandler.WebModuleClass := TWebModule1;
    end.



et:

  • la mécanique est basée sur les composant WebBroker. Ceux-ci comportent un tWebModule, qui peut contenir zéro ou plusieurs tWebActionItems. Supposons que nous ayons créé ("click droit souris | Action Editor | Add etc) trois actions "un/" "deux/" et "trois/", avec chacune un événement OnAction. Si un client demande

        http://url_serveur/p_nom_serveur/deux"

    alors deuxAction sera exécuté. Il y a en plus toute une mécanique qui permet

    • de traiter ou non un action
    • de terminer après le traitement d'une action (Handled à True)
    • de laisser WebBroker exécuter les actions qui suivent dans la liste
    • de créer une action par défaut qui sera exécutée si aucune action précédente n'a terminé le traitement
    Les noms "un", "deux" et "trois" de notre exemple fictif sont appelés PathInfo

  • dans notre cas
    • une action par défaut ayant un PathInfo "soap/" a été automatiquement créée dans chaque extension Serveur Web Service
    • une autre action "wsdl/" a été créée, et c'est l'action par défaut
  • le tWebModule contient
    • HTTPSoapDispatcher1 qui est chargé de réceptionner les demandes Client Web Services, et les orienter vers HTTPSoapPascalInvoker1, en fonction de PathInfo
    • HTTPSoapPascalInvoker1 est chargé de transformer le nom d'une méthode (par exemple la chaîne "f_dollar_to_euro") en un appel de la méthode correspondante (dans notre cas la FUNCTION f_serveur_euro)
    • WSDLHTMLPublish1 est chargé de générer le fichier .WSDL qui décrit nos deux méthodes


En effet, le WebModule contient aussi une procédure WebModule1DefaultHandlerAction. Cette événement correspond à un ActionItem créé automatiquement par le Wizard. Nous pouvons le voir de la façon suivante:
   sélectionnez la forme du web modules et effectuez un clic droit
   le dialogue d'édition des actions est affiché, avec l'action créée par défaut:

web_module_wdsl_publication_action



L'action qui provoque les calculs (et pas la fourniture du fichier .WSDL) est implicite (pas affichée dans l'éditeur d'actions).



3.1.5 - La fenêtre principale

Comme nous avons créé une application WAD, elle a une fenêtre (ce ne serait pas le cas pour les ISAPI ou CGI purs).

Nous n'avons pas besoin d'y toucher. En général nous la colorions un peu pour pouvoir la visualiser plus facilement, et nous la positionnons à droite de l'écran (création de tForm.OnCreate et initialisation de Left et Top) pour éviter que Delphi ne positionne la fenêtre de façon plutôt aléatoire.



3.2 - Compilation de notre Serveur Web

3.2.1 - Indy 9

Le Serveur HTTP intégré WAD est construit à partir du Serveur HTTP Indy.

Pour Delphi 2006, il s'avère que ce serveur WAD a été compilé avec Indy 9.

Lors de l'installation de Delphi 2006, il nous a été demandé de choisir entre Indy 9 et Indy 10. Si vous avez sélectionné Indy 9, vous pouvez sauter ce paragraphe.

Si vous avez sélectionné Indy 10, il faut permettre au compilateur de retrouver les .DCU correspondant à Indy 9. Pour cela:
   sélectionnez C:\Program Files\Borland\BDS\4.0\lib\Indy9

import_indy_9

et collez cette adresse dans le press papier

   sélectionnez "Project | Options | Directories | Search Path" et collez-y ce chemin


3.2.2 - Compilation du Serveur

Nous compilons notre projet
   tapez F9
   la sécurité Windows se rappelle à notre bon souvenir:

alerte_securie_windows

   tapez "débloquer"


Notez que:
  • il FAUT compiler au moins une fois. Le résultat est de stocker une correspondance entre notre exécutable et notre service u_i_server_euro_dollar-i_server_euro_dollar dans la base de registre:

    wad_server_registry_entry

    La connaissance de cette entrée permette éventuellement de faire le ménage (ce qui est aussi possible depuis le WAD)

  • c'est cette entrée qui permet de lancer le CGI sous contrôle du Web App Debugger

  • lorsque nous avons autorisé Windows à lancer notre Serveur, cela correspond à une nouvelle entrée dans "Démarrer | Paramètres | Panneau de Configuration | Pare feu Windows | Exception":

    pare_feu_windows

    Une autre information utile pour faire le ménage de temps en temps.



3.3 - Création du fichier WSDL

3.3.1 - Pourquoi créer le fichier .WSDL

Normalement, pour créer le Client, nous n'avons pas besoin de créer un fichier physique correspondant: nous pouvons interroger le Serveur Web Service pour lui demander ces données. Notre version de Delphi 2006 ne permet cependant pas de récupérer ces données par une URL. En revanche l'importation depuis un fichier sur disque fonctionne.



Enregistrement de ServerInfo

Le Web App Debugger va nous présenter une liste des CGI disponibles. En fait, il s'agit d'un service Web qui nous fournit la liste de NOS services web (ce service interroge simplement la base de registres !).

Si vous venez d'installer Delphi 2006 (ou que vous n'avez jamais créé de Services Web), SERVERINFO.EXE n'est pas enregistré.

Pour l'enregistrer la première fois:
   sélectionnez C:\Program Files\Borland\BDS\4.0\Bin

start_serverinfo_once

et lancez SERVERINFO.EXE



Notez que:

  • Cette opération n'aura plus besoin d'être effectuée
  • à titre anectdotique, SERVERINFO figurait bien dans la liste des WAD de la base de registre (image avant la précédente)


3.3.2 - Lancement de notre Serveur Web Service

Lançons à présent notre CGI:
   fermez l'exécutable du Serveur
   sélectionnez "Tools | Web App Debugger"
   la fenêtre du Web App Debugger est affichée:

web_app_debugger

   cliquez "Start" pour préparer la liste des CGI disponibles
   le lien "http://localhost:8081/ServerInfo.ServerInfo" est utilisable et comporte un souligné

   cliquez le souligné pour demander à SERVERINFO de nous fournir la liste des Serveurs
   ServerInfo retourne une page Web, avec une tListBox contenant les CGI disponibles, dont notre Serveur (que nous avons sélectionné):

serverinfo_returns_servers

   sélectionnez le Serveur que vous souhaitez lancer. Dans notre cas, c'est p_server_euro_dollar_wad.co_euro_dollar, et cliquez "Go"
   une nouvelle page Web est affichée avec les informations concernant notre Service Web:

web_server_information



Sur la dernière figure
  • trois fenêtre sont à l'écran:
    • la fenêtre d'information sur les Services Web disponibles actuellement, soit 2 services
    • la fenêtre de ServerInfo (A)
    • la fenêtre de notre service en jaune (B)
  • sur la fenêtre d'information, nous avons une partie pour chaque service (1 et 2).
  • prenons notre service: en (3) il y a un hyper-lien qui permet de visualiser le fichier WSDL


3.3.3 - Consultation et Sauvegarde du .WSDL

Pour visualiser le .WSDL:
   cliquez sur le lien (3)
   le texte du .WSDL est affiché dans une fenêtre Web:

our_service_wsdl

Sans rentrer dans le détail des données .WSDL, vous pouvez reconnaître des information sur les méthodes et les paramètres de ce qui est offert par notre service



Pour sauvegarder les données dans un fichier
   sélectionnez "Fichier | Enregistre Sous" et placez le fichier dans le même répertoire que les autres fichiers (le nom i_server_euro_dollar.xml est proposé par défaut et nous le conservons)



4 - Création d'un Client Web Service

4.1 - Consommation du Service

4.1.1 - Invocation d'une méthode Serveur

Nous allons à présent créer un Client qui va appeler nos méthodes depuis une application connectée à notre Serveur par TCP/IP.

Nous pourrions construire manuellement et envoyer la requête SOAP au Serveur, puis récupérer la réponse et la décortiquer. Cette solution est possible en utilisant des Sockets TCP/IP. C'est en fait la solution que nous avions adoptée pour notre conférence SOAP et DataSnap à la conférence Borland en 2001.

Delphi utilise une technique qui automatise ces échanges sans que nous ayons à traiter les messages SOAP. Au niveau principe:

  • Delphi construit en mémoire un composant qui a les mêmes méthodes que la CLASSe du Serveur. Ce composant est appelé un Proxy : il se comporte, du point de vue du Client comme le véritable composant Serveur.

    Voici la situation après le lancement du Serveur et du Client:

    web_service_client_proxy

    où nous avons représenté

    • l'INTERFACE i_server_euro sur le Serveur
    • le Proxy i_client_euro sur le Client
  • l'application Client effectue sa demande en appelant une méthode du Proxy

    client_app_calls_the_proxy

  • le Proxy transforme cette requête en un message SOAP qui est envoyé au Serveur:

    web_service_client_soap_request

  • le Serveur calcule sa réponse, qui est retournée au Proxy, lequel retourne la valeur à l'appelant:

    web_service_client_proxy_



Le résultat de cette mécanique est que le Client effectue ses appels de FUNCTION ou de PROCEDURE "comme si" la CLASSe Serveur avait été téléchargée sur son PC.

Le Proxy est mis en oeuvre en utilisant un composant tHttpRIO (RIO= Remote Invocation Object)



4.1.2 - Les composants Delphi

Le composant tHttpRIO a pour objectif de nous retourner une INTERFACE correspondant à l'INTERFACE sur le Serveur.

Pour cela, il faut que le Client ait la définition Pascal de cette INTERFACE

Et pour importer la définition de l'INTERFACE, nous utilisons un Wizard qui:

  • lit un .WSDL (par son URL ou en lisant un fichier .XML)
  • construit l'UNITé contenant:
    • l'INTERFACE de notre Service (ainsi que les autres définitions si notre Service comporte des types ou CLASSes annexes particulières, ce qui n'est pas le cas pour notre exemple)
    • en bonus, une FUNCTION qui retourne automatiquement cette INTERFACE
Il s'avère que nous ne pouvons invoquer le Wizard qu'après avoir créé une application Client.

Nous allons donc

  • créer une application Client
  • créer à l'aide du Wizard l'UNITé qui contiendra l'INTERFACE
  • compléter, dans la tForm de notre client, quelques appels de notre Service


4.2 - Création du Client

Il s'agit d'une application Delphi des plus classiques:
   sélectionnez "File | New | Vcl Application Form"
   sauvegardez le projet. Nous suggérons de créer un répertoire CLIENT\ et de nommer les fichiers U_F_CLIENT_EURO_DOLLAR et P_CLIENT_EURO_DOLLAR
   compilez pour vérifier que tout est en ordre


Notez que
  • vous pourriez aussi ajouter la nouvelle application au groupe par "Project Manager | ProjectGroup1 | Add New Project etc


4.3 - Importation de l'INTERFACE par le Wizard

Maintenant nous pouvons utiliser le Wizard qui va transformer le .WSDL en une CLASSe locale du Client:
   sélectionnant "File | New | Other | Delphi Project | Web Services | Wsdl Importer"

wsdl_importer

et cliquez "Ok"

   le Wsdl Import Wizard est affiché (Ed: tronqué en largeur)

wsdl_importer
wsdl_import_wizard

   dans "Location of WSDL File", tapez le chemin et le nom du fichier .XML. Dans notre cas:

    C:\programs\fr\web\web_service_tutorial\server_wad\i_server_euro_dollar.xml

(vous pouvez utiliser l'ellipse ... pour localiser ce fichier

   cliquez sur "Finish"

   l'unité est présentée (Ed: tronqué en largeur):

wsdl_import_wizard_preview

   cliquez "Finish"
   l'unité est copiée dans notre projet Client, sous le nom de I_SERVER_EURO_DOLLAR1
   nous suggérons de renommer l'UNITé U_I_CLIENT_EURO_DOLLAR


L'unité d'importation comporte (en enlevant les commentaires):

unit u_i_client_euro_dollar1;
  interface
    uses InvokeRegistrySOAPHTTPClientTypesXSBuiltIns;

    type i_server_euro_dollar =
           interface(IInvokable)
           '{091A6317-83B7-FB51-3FD6-4BD47A83325F}']
           function  f_dollar_to_euro(const p_dollarDouble): Doublestdcall;
           procedure euro_to_dollar(const p_euroDouble;
               var pv_dollarDouble); stdcall;
         end// i_server_euro_dollar

    function Geti_server_euro_dollar(UseWSDLBoolean=System.False;
        Addrstring=''HTTPRIOTHTTPRIO = nil): i_server_euro_dollar;

  implementation

    function Geti_server_euro_dollar(UseWSDLBoolean;
        AddrstringHTTPRIOTHTTPRIO): i_server_euro_dollar;
      const
          defWSDL = 'C:\programs\fr\web\web_service_tutorial\server_wad\'
              + 'i_server_euro_dollar.xml';
          defURL  = 'http://localhost:8081/'
              + 'p_serveur_euro_dolar_wad.co_euro_dollar/soap/'
              + 'i_server_euro_dollar';
          defSvc  = 'i_server_euro_dollarservice';
          defPrt  = 'i_server_euro_dollarPort';
      var RIOTHTTPRIO;
      begin
        Result := nil;
        if (Addr = '')
          then begin
              if UseWSDL
                then Addr := defWSDL
              else Addr := defURL;
            end;

        if HTTPRIO = nil
          then RIO := THTTPRIO.Create(nil)
          else RIO := HTTPRIO;

        try
          Result := (RIO as i_server_euro_dollar);
          if UseWSDL
            then begin
                RIO.WSDLLocation := Addr;
                RIO.Service := defSvc;
                RIO.Port := defPrt;
              end
            else RIO.URL := Addr;
        finally
          if (Result = niland (HTTPRIO = nil)
            then RIO.Free;
        end// try ... finally
      end// Geti_server_euro_dollar

      initialization
        InvRegistry.RegisterInterface(TypeInfo(i_server_euro_dollar),
            'urn:u_i_server_euro_dollar-i_server_euro_dollar''utf-8');
        InvRegistry.RegisterDefaultSOAPAction(TypeInfo(i_server_euro_dollar),
            'urn:u_i_server_euro_dollar-i_server_euro_dollar#%operationName%');
      end.

et:

  • nous retrouvons bien l'INTERFACE que nous avions côté Serveur
  • la fonction Geti_server_euro_dollar permet simplement de nous retourner une INTERFACE, avec plusieurs possibilités
    • si nous ne fournissons aucun paramètres, un tHttpRio local est créé, et est retourné transtypé en i_server_euro_dollar
    • si nous demandons un .WSDL, i_server_euro_dollar pourra être utilisé pour récupérer le .WSDL
    • nous pouvons aussi placer un tHttpRio sur la tForm Client, et fournir celui-ci comme paramètre après l'avoir initialisé


Comme nous appellerons Geti_server_euro_dollar sans paramètres, nous pouvons (c'est optionnel) simplifier cette fonction.

Voici les modifications (cosmétiques) que nous apportons:

  • changez le nom i_server_euro_dollar en i_client_euro_dollar (l'INTERFACE correspond à un proxy)
  • supprimez les paramètres de Geti_server_euro_dollar
  • veillez à ce que les chaînes littérales du bas appellent bien 'i_server_euro_dollar': ce sont elles qui seront utilisées pour appeler le Serveur
Voici notre unité modifiée:

unit u_i_client_euro_dollar;
  interface
    uses InvokeRegistrySOAPHTTPClientTypesXSBuiltIns;

    type i_client_euro_dollar =
           interface(IInvokable)
             ['{091A6317-83B7-FB51-3FD6-4BD47A83325F}']
             function  f_dollar_to_euro(const p_dollarDouble): Doublestdcall;
             procedure euro_to_dollar(const p_euroDouble;
                 var pv_dollarDouble); stdcall;
           end// i_client_euro_dollar

    function f_i_client_euro_dollari_client_euro_dollar;

  implementation

    function f_i_client_euro_dollari_client_euro_dollar;
      const k_service_url'http://localhost:8081/'
              + 'p_serveur_euro_dolar_wad.co_euro_dollar/soap/'
              + 'i_server_euro_dollar';
      var l_c_http_remote_invokation_objectTHTTPRIO;
      begin
        l_c_http_remote_invokation_object:= THTTPRIO.Create(nil);
        Try
          Result := (l_c_http_remote_invokation_object as i_client_euro_dollar);
          l_c_http_remote_invokation_object.URL:= k_service_url
        finally
          if Result = nil
            then l_c_http_remote_invokation_object.Free;
        end// try ... finally
      end// f_i_client_euro_dollar

    initialization
      InvRegistry.RegisterInterface(TypeInfo(i_client_euro_dollar),
          'urn:u_i_server_euro_dollar-i_server_euro_dollar''utf-8');
      InvRegistry.RegisterDefaultSOAPAction(TypeInfo(i_client_euro_dollar),
          'urn:u_i_server_euro_dollar-i_server_euro_dollar#%operationName%');
    end.



Notez que

  • les deux appels de InvRegistry correspondent aussi à des enregistrement "locaux" (pas dans la Registry Windows) et permettent de transformer nos appels de i_client_euro_dollar en des requêtes HTTP au format SOAP
  • le GUID de l'INTERFACE importée n'a rien à voir avec le GUI sur le Serveur. Ici aussi ce GUID sert uniquement à identifier de façon unique cette INTERFACE dans la liste de correspondance locale au Client


4.4 - Utilisation de notre Proxy

Finissons par le programme Client. Nous allons simplement effectuer une conversion d'une valeur contenue dans un tEdit, et la conversion sera effectuée lorsque nous cliquons sur un tButton:
   sélectionnez la tForm principale
   placez un tEdit, un tLabel et un tButton sur Form1
   créez l'événement Button1Click et tapez le code qui convertira un montant (nous avons utilisé deux locales auxiliaires qui auraient pu être évitées):

procedure TForm1.dollar_to_euro_Click(SenderTObject);
  var l_euro_amountl_dollar_amountDouble;
  begin
    l_dollar_amount:= StrToFloat(dollar_edit_.Text);

    // -- appel du service
    l_euro_amount:=
      f_i_client_euro_dollar().f_dollar_to_euro(l_dollar_amount);

    euro_label_.Caption:= FloatToStr(l_euro_amount);
  end// dollar_to_euro_Click

   importez aussi U_I_CLIENT_EURO_DOLLAR dans la Forme principale
   compilez, exécutez, tapez un montant en dollar, cliquez sur le bouton
   voici le résultat:

convert_euro_to_dollar



Notez que
  • nous avons pu exécuter directement notre Client, car notre Serveur était encore chargé (même si le projet Serveur a été fermé, le CGI reste lancé par le Web App Debbugger)


Le Web App Debugger stocke les messages échangés entre le Client et le Serveur:
   sélectionnez l'icône du WAD sur la barre de tâche
   sélectionnez l'onglet "Log"
   la liste des messages HTTP est présentée:

wad_message_log

   cliquez sur l'avant dernier message, correspondant à notre requête
   le texte du message apparaît dans une fenêtre, avec possibilité de visualiser les messages précédents, suivants etc

web_app_debugger_soap_message_display




5 - Améliorations et Commentaire

5.1 - Compliqué ?

Il est à craindre que la lecture de ce document ne fasse apparaître les Services Web comme horriblement compliqués. Pourtant Delphi a fait des efforts considérables pour limiter les manipulations au plus strict nécessaire. En réalité, le fait que nous ayons passé du temps à expliquer certains fonctionnement a considérablement alourdi la présentation.

Si vous créez deux ou trois services

  • les manipulations initiales (comme le premier lancement de ServerInfo, par exemple) n'auront pas à être répétées
  • l'utilisation d'un groupe de projet simplifie énormément les aller-retour lorsque nous modifions l'INTERFACE du Service
  • les différentes manipulations deviendront automatiques


Nous avons même simplifié le traitement en créant un programme de transformation qui fonctionne ainsi:
  • nous avons créer un Service Web (Serveur et Client), en nommant le "service" (l'équivalent de "euro_dollar") "xxx"
  • notre utilitaire copie les fichiers de cette application simple vers un autre répertoire en renommant "xxx" par le nom que nous avons fourni
Dans ce cas, il ne reste plus qu'à
  • au niveau Serveur, entrer les méthodes du Service, et les implémenter
  • au niveau Client:
    • copier les méthodes dans le client
    • taper le code utilisateur du Client


5.2 - Conventions de noms

Nous avons utilisé des convention de préfixes forts (i_xxx, u_i_xxx, c_xxx ainsi que le mot "serveur" pour le Serveur et "client" pour le Client, et les suffixes "_wad", "_cgi" et "_isapi" pour distinguer les différents types d'extension Serveur).

Comme d'habitude, vous n'êtes pas obligés de suivre ces conventions. En particulier, le fait de supprimer "serveur" et "client" dans le nom de l'INTERFACE (par exemple iEuroDollar au lien de i_serveur_euro_dollar et i_client_euro_dollar) permet quasiment d'utiliser la même INTERFACE pour le Serveur et le Client

Mais nous restons, quant à nous, fidèles aux préfixes. Dans ce cas, ils nous ont permis de bien comprendre les relations entre les différentes pièces qui entrent en jeu. Si tous les identificateurs sont identiques, il est aisé de croire qu'il s'agit d'un objet alors qu'il s'agit d'un autre.

Et en utilisant l'utilitaire de transformation, les manipulation concernant les préfixes sont quasiment inexistantes.



5.3 - Ce qui manque

Cet exemple simple avait pour unique but de montrer comment construire un service web. Nous aurions pu ajouter
  • le traitement de types complexes (tableaux, CLASSes, tDataSets,
  • le transfert de données (fichiers) binaires
  • les pièces attachées (SOAP Attachments)
  • les transferts de données au format .XML
  • les échanges avec Datasnap, en utilisant des tSoapConnection
  • la création d'extensions CGI et ISAPI
  • le déploiement sur des Serveurs HTTP (IIS, Apache)
  • le débugging du Serveur Web Service en utilisant le Web App Debugger
  • les conversions entre des applications CGI, ISAPI et Web App Debugger
  • les diagrammes de Classe UML présentant les différents composants Delphi (tWebApp, tHttpRio ...) mis en oeuvre


5.4 - Formation Web Service

Les points mentionnés ci-dessus sont abordés lors de nos Formations XML Web Services. Nous avons regroupé la présentation .XML avec la présentation Services Web, car une connaissance du format .XML est utile, et il arrive que les applications Service Web aient à traiter des données au format .XML, indépendemment de SOAP.



5.5 - Et .Net dans tout cela ?

Notre présentation a été effectuée en mode Win32. En fait les Services Web correspondent à un format, lequel peut être implémenté et utilisé sur la plupart des ordinateur (Mac, PC), sous divers Operating Systems (Windows, Linux, Unix, MacOs).

Par conséquent ils peuvent aussi être utilisés (Serveur et Client) dans l'environnement .NET. Ce qui change ce sont les composants et les manipulations de mise en oeuvre.

Celles-ci sont d'ailleurs présentées lors de nos formations Delphi 2006 .Net ainsi que pendant les formations ASP.NET Delphi et ASP.NET 2.0 Delphi



5.6 - Next

Au niveau explications, nous aurions aussi pu détailler
  • le fonctionnement de WebBroker
  • la mécanique de iInvoke


Il existe une étape au-delà des Services Web : les objets distribués. Pour expliquer la hiérarchie, nous avons
  • les extensions CGI, qui nécessitent un explorateur Web, l'appel d'une page contenant une balise <FORM
  • les Services Web, que l'utilisateur peut invoquer depuis n'importe quelle application exécutable (pas besoin d'explorateur internet)
    Ces services sont limités à des appels de FUNCTIONs et de PROCEDUREs. Les traitement réalisés peuvent être très compliqué (modifier une base de donnée, retourner la carte de votre domicile), mais comme il s'agit d'INTERFACE, la mécanique de base ne gère pas d'état. Une fois la réponse retournée, le Serveur ne se souvient plus de vous. Si votre traitement nécessite plusieurs étapes (lecture paginée de Tables), c'est à vous de gérer ces sessions au niveau Client
  • les objets distribués: comme les objets contiennent des données, ils sont plus puissants qu'une simple collection de méthodes
Nous présentons les objets distribuées lors de nos formations .Net




6 - Télécharger le code source Delphi

Vous pouvez télécharger: Ce .ZIP qui comprend:
  • les fichiers .DPR, PAS, DFM
  • le fichier .WSDL
Pour l'utiliser:
  • dézippez, sélectionnez le groupe, compilez
Ce .ZIP ne modifie pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.



Comme d'habitude:

  • nous vous remercions de nous signaler toute erreur, inexactitude ou problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections qui en résulteront pourront aider les prochains lecteurs
  • tous vos commentaires, remarques, questions, critiques, suggestion d'article, ou mentions d'autres sources sur le même sujet seront de même les bienvenus à jcolibri@jcolibri.com.
  • plus simplement, vous pouvez taper (anonymement ou en fournissant votre e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en cliquant "envoyer" :
    Nom :
    E-mail :
    Commentaires * :
     

  • et si vous avez apprécié cet article, faites connaître notre site, ajoutez un lien dans vos listes de liens ou citez-nous dans vos blogs ou réponses sur les messageries. C'est très simple: plus nous aurons de visiteurs et de références Google, plus nous écrirons d'articles.



7 - Références

Quelques références concernant les Services Web:
  • Web Services in Delphi 2007 par Bruneau BABET de CodeGear - conférence en ligne CodeRage I - Mars 2007 - Video .SWF de 39 Meg.
    Mentionnons aussi que Bruneau BABET est très présent au niveau des newsgroup Delphi (borland.public.delphi.webservices.soap), et répond bien volontiers aux questions qui sont posées.

  • au niveau de nos articles:
  • présentation SOAP et DataSnap : les transparent de notre présentation à la conférence Borland en 2001.


Pour des Serveurs HTTP capables de traiter SOAP:
  • pour le Serveur CGI en source à partir des Sockets Delphi. Ce serveur capable de gérer des requêtes CGI ne permet pas de traiter des messages SOAP en l'état
  • il a été modifié pour permettre SOAP, mais cette version n'a pas été publiée à ce jour
  • il existe plusieurs serveurs à base d'Indy, démarrés par Dave NOTTAGE, et amélioré par la suite. Google ou Code Central vous permettront de les récupérer


Les points mentionnés ci-dessus sont abordés lors de nos différentes formations:


8 - L'auteur

John COLIBRI est passionné par le développement Delphi et les applications de Bases de Données. Il a écrit de nombreux livres et articles, et partage son temps entre le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le conseil (composants, architecture, test) et la formation. Son site contient des articles avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client.
Created: jan-04. Last updated: mar-2020 - 250 articles, 620 .ZIP sources, 3303 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2020
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
    + web_internet_sockets
      – http_client
      – moteur_de_recherche
      – javascript_debugger
      + asp_net
      – client_serveur_tcp
      – site_editor
      – pascal_to_html
      – cgi_form
      + les_logs_http_bruts
      + les_blogs
      – intraweb_tutorial
      + services_web
        – delphi_web_services
      – serveur_web_iis
      – client_serveur_pop3
      – lecteur_mails_free
    + services_web_
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
    + admin
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Expert Delphi Résolution de problèmes ponctuels, optimisation, TMA Delphi, audit, migration, réalisation de projets, transfert de technologie - Tél 01.42.83.69.36
Formation Initiation Delphi L'outil de développpement, le langage de programmation, les composants - 3 jours
Formation Programmation Objet Delphi La programmation objet: les types, l'encapsulation, l'héritage, le polymorphisme - 3 jours